using BLIT64;

namespace TBit.GUI {
    public class VMScene : Scene {
        public VM vm;
        public string instruction_string = "Instructions";
        public int instruction_x = 10;
        public int instruction_y = 10;
        public int data_i = 0;

        public string register_string = "Register   ";
        public int register_x = 10;
        public int register_y = 10;
        
        public override void Load()
        {
            var (w,h) = BLIT64.Game.Instance.WindowSize;
            register_x = w - register_string.Length * 8 * 2 - 10;
            register_y = 10;
        }

        public override void Update()
        {
            /*
                Enter : 执行
                R ： 重置
            */
            if(BLIT64.Input.KeyPressed(Key.Enter))
            {
                vm.RunNext();
            }

            if(BLIT64.Input.KeyPressed(Key.R))
            {
                vm.Reset();
            }

            if(Input.KeyDown(Key.D) && Input.KeyPressed(Key.Down))
            {
                if(data_i < vm.memory.Count - 5)
                {
                    data_i++;
                }
            }

            if(Input.KeyDown(Key.D) && Input.KeyPressed(Key.Up))
            {
                if(data_i > 0)
                {
                    data_i--;
                }
            }
        }

        public string GetRegisterString(RegisterIndex reg) {
            switch(reg)
            {
                case RegisterIndex.R1 : return "R1";
                case RegisterIndex.R2 : return "R2";
                case RegisterIndex.R3 : return "R3";
                case RegisterIndex.R4 : return "R4";
                case RegisterIndex.RET : return "RET";
                case RegisterIndex.CP : return "CP";
            }
            return "233";
        }

        public string GetDataString(Data data) {
            string str = "";
            switch(data.type)
            {
                case DataType.Number : str = data.number_data.ToString(); break;
                case DataType.Boolean : str = data.boolean_data.ToString(); break;
                case DataType.String : str = data.string_data; break;
                case DataType.Null : str = "Null"; break;
            }
            return str;
        }

        public string GetInstructionArgString(InstructionArgData arg)
        {
            string str = "";
            switch(arg.type)
            {
                case InstructionArgDataType.Data : str = $"D[{GetDataString(arg.data)}]";break;
                case InstructionArgDataType.Variable : str = $"V[{arg.variable}]";break;
                case InstructionArgDataType.Register : str = $"R[{GetRegisterString(arg.register)}]";break;
            }
            return str;
        }

        public string GetOpcodeString(Opcode opcode) {
            switch(opcode)
            {
                case Opcode.MOV : return "MOV";
                case Opcode.DW : return "DW";
                case Opcode.DD : return "DD";
                case Opcode.ADD : return "ADD";
                case Opcode.SUB : return "SUB";
                case Opcode.MUL : return "MUL";
                case Opcode.DIV : return "DIV";
                case Opcode.JMP : return "JMP";
                case Opcode.OFSJMP : return "OFSJMP";
                case Opcode.CJMP : return "CJMP";
                case Opcode.OFSCJMP : return "OFSCJMP";
                case Opcode.EQ : return "EQ";
                case Opcode.NEQ : return "NEQ";
                case Opcode. LE : return "LE";
                case Opcode.GR : return "GR";
                case Opcode.PUSH : return "PUSH";
                case Opcode.POP : return "POP";
                case Opcode.POPR : return "POPR";
                case Opcode.CALL : return "CALL";
                case Opcode.DF : return "DF";
                case Opcode.RET : return "RET";
                case Opcode.AND : return "AND";
                case Opcode.OR : return "OR";
                case Opcode.NOT : return "NOT";
            }
            return "Undefined";
        }

        public string GetInstructionString(Instruction inst)
        {
            string str = GetOpcodeString(inst.opcode);
            foreach(InstructionArgData arg in inst.args)
            {
                str += " " + GetInstructionArgString(arg);
            }
            return str;
        }

        public override void Draw(Blitter blitter)
        {
            Game.Title = $"TBit VM       pc : {vm.pc}";
            blitter.Clear(8);
            blitter.Text(instruction_x,instruction_y,instruction_string,2,0);
            blitter.Text(register_x,register_y,register_string,2,0);

            int x = instruction_x;
            int y = instruction_y + 30;
            int pc = vm.pc;
            int pc_up = pc - 8;
            int pc_down = pc + 8;

            for(int i = pc_up;i < pc;i++)
            {
                if(i >= 0 && i < vm.instructions.Count)
                {
                    var inst = vm.instructions[i];
                    blitter.Text(x,y,$"{i}. {GetInstructionString(inst)}",2,255);
                    y += 20;
                }
            }
            if(vm.pc >= 0 && vm.pc < vm.instructions.Count)
            {
                string str = $"{vm.pc}. {GetInstructionString(vm.instructions[vm.pc]) }";
                blitter.Rect(x,y,str.Length * 8 * 2,16,21);
                blitter.Text(x,y,str,2,32);
                y += 20;
            }
            for(int i = pc + 1;i <= pc_down;i++)
            {
                if(i >= 0 && i < vm.instructions.Count)
                {
                    var inst = vm.instructions[i];
                    blitter.Text(x,y,$"{i}. {GetInstructionString(inst)}",2,255);
                    y += 20;
                }
            }
            if(vm.pc >= vm.instructions.Count)
            {
                blitter.Text(x,y,"End",2,32);
            }

            x = register_x;
            y = register_y + 30;
            for(int i = 0;i < VM.REGISTER_NUMBER;i++)
            {
                var reg = vm.registers[i];
                blitter.Text(x,y,$"R{i+1} : {GetDataString(reg)}",2,10);
                y += 40;
            }

            blitter.Text(x,y,"Datas",2,0);
            y += 20;

            for(int i = data_i;i < data_i + 5;i++)
            {
                if(i >= 0 && i < vm.memory.Count)
                {
                    if(vm.memory_name_hash.ContainsKey(i))
                    {
                        var data = vm.memory[i];
                        blitter.Text(x,y,$"{vm.memory_name_hash[i]}:{GetDataString(data)}",2,5);
                        y += 20;
                    }
                }
            }
        } 
    }
}